`timescale 1ns/1ps

// CCD驱动状态机模块

module ccd_drv_sm (
    input clk_1M,           // 状态机的系统时钟,默认采用1MHz
    input clk_reg,          // 对应1个像元周期的时钟
    input sm_trig,          // 触发状态机开始工作的信号
    
//    input[15:0] vtrans_num,
    input[15:0] htrans_num,

    input vtrans_done,
    input htrans_done,
    output vtrans_en,
    output htrans_en,
    output status_vtrans,   // tell us if a vertical line transfer is finished
    output status_htrans    // tell us if all registers of a row of pixels havs been readout
);

//  状态的定义
    localparam IDLE         = 4'd0; // 空闲状态
    
    //  第一行空读状态
    localparam S_HT0_PRE    = 4'd1;
    localparam S_HT0        = 4'd2;
    localparam S_HT0_POS    = 4'd3;
    
    // 水平转移
    localparam S_HT_PRE     = 4'd4; // 水平转移开始前的状态，需要调整好 RPhi_x & Phi_rst，并将CCD231_Line_Transfer的复位信号拉高，并在进入S_LT状态后再拉低
    localparam S_HT         = 4'd5; // 水平转移状态
    localparam S_HT_POS     = 4'd6; // 水平转移状态结束后、进入垂直转移之前的状态
    
    // 垂直转移（像元读出）
    localparam S_VT_PRE     = 4'd7; // 垂直转移前
    localparam S_VT         = 4'd8; // 垂直转移状态
    localparam S_VT_POS     = 4'd9; // 垂直转移状态结束后、进入水平转移之前的状态
    
    localparam S_SLEEP      = 4'd10;

//  状态寄存器
    reg[3:0] state_c = S_SLEEP;
    reg[3:0] state_n;


    reg[15:0] vtrans_cnt;   //  vertial transfer number counts
    reg[15:0] htrans_cnt;   //  horizontal transfer number counts

//  状态迁移条件
    wire idle2ht0pre;   // ILDE -> S_HT0_PRE
    wire ht0pre2ht0;    // S_HT0_PRE --> S_HT0
    wire ht02ht0pos;    // S_HT0 --> S_HT0_POS
    wire ht0pos2htpre;  // S_HT0_POS --> S_VT_PRE
    wire htpre2ht, ht2htpos, htpos2vtpre;
    wire vtpre2vt, vt2vtpos, vtpos2htpre;
    wire vtpos2sleep;  //当所有读出完成之后，进入sleep状态

    wire vtpose2sleep;

//  同步时序描述“次态”迁移到“现态”，以像元周期为基本时间单位
    // always @(posedge clk_1M or posedge sm_trig) begin // 用上升沿触发会导致问题！！！
    always @(negedge clk_1M or posedge sm_trig) begin
        if (sm_trig) begin
            state_c <= IDLE;
        end
        else begin 
            state_c <= state_n;
        end
    end

//  状态转换条件
    assign idle2ht0pre  = (state_c == IDLE);                                    // ILDE -> S_RT0_PRE
    
    assign ht0pre2ht0   = (state_c == S_HT0_PRE);                               // S_RT0_PRE --> S_RT0
    assign ht02ht0pos   = (state_c == S_HT0) && (htrans_cnt == htrans_num+1); // S_RT0 --> S_RT0_POS
    assign ht0pos2vtpre = (state_c == S_HT0_POS);                               // S_RT0_POS --> S_LT_PRE
   
    assign vtpre2vt     = (state_c == S_VT_PRE) ;
    assign vt2vtpos     = (state_c == S_VT) && (vtrans_done == 1'b1);
    assign vtpos2sleep  = (state_c == S_VT_POS);

//  时序逻辑描述状态转移条件判断
    always@(*) begin
        case( state_c )
            IDLE: begin
                if( idle2ht0pre ) begin
                    state_n = S_HT0_PRE;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0_PRE: begin
                if( ht0pre2ht0 ) begin
                    state_n = S_HT0;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0: begin
                if( ht02ht0pos ) begin
                    state_n = S_HT0_POS;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_HT0_POS: begin
                if( ht0pos2vtpre ) begin
                    state_n = S_VT_PRE;
                end
                else begin
                    state_n = state_c;
                end
            end
            
            S_VT_PRE: begin
                if( vtpre2vt ) begin
                    state_n = S_VT;
                end
                else begin
                    state_n = state_c;
                end
            end

            S_VT: begin
                if( vt2vtpos ) begin
                    state_n = S_VT_POS;
                end 
                else begin
                    state_n = state_c;
                end     
            end

            S_VT_POS: begin
                // if( vtpos2htpre ) begin
                //     state_n = S_HT_PRE;
                // end 
                if( vtpos2sleep ) begin
                    state_n = S_SLEEP;
                end 
                else begin
                    state_n = state_c;
                end     
            end

            S_SLEEP: begin
                state_n = S_SLEEP;
            end

            default: begin
                state_n = S_SLEEP;
            end
        endcase
    end

    assign htrans_en = (state_c == S_HT) || (state_c == S_HT0);
    assign vtrans_en = (state_c == S_VT);

// 水平读出计数器htrans_cnt
    always@(posedge clk_reg or posedge sm_trig) begin
        if(sm_trig) begin
            htrans_cnt <= 16'd0;
        end
        else if( state_c == S_HT_PRE || state_c == S_HT || state_c == S_HT0_PRE || state_c == S_HT0 ) begin
            htrans_cnt <= htrans_cnt + 16'd1;
        end
        else if( state_c == S_VT_PRE || state_c == IDLE ) begin
            htrans_cnt <= 16'd0;   //在每一行数据读出完成之后或者回到IDLE状态时，需要归零！！！！
        end
    end

    assign status_htrans= (state_c == S_HT_POS) && ( htrans_cnt == (htrans_num + 3) ); // 注意最后的+3
    assign status_vtrans= (state_c == S_VT_POS);

//  垂直转移计数器vtrans_cnt
    always@(posedge clk_1M or posedge sm_trig) begin
        if(sm_trig) begin
            vtrans_cnt <= 16'd0;   // 仅在sm_trig信号拉高时将行计数器归零
        end
        else if( state_c == S_VT_POS ) begin
            vtrans_cnt <= vtrans_cnt + 16'd1;
        end
        // 注意：除非reset，否则不能归零！！！！
    end
endmodule
